Add WebSocket-based live reload for development#86
Merged
antosubash merged 7 commits intomainfrom Apr 5, 2026
Merged
Conversation
Implement automatic browser refresh when Vite builds or Tailwind CSS compilation completes during development. This eliminates the need to manually refresh the browser after editing frontend code. Key changes: - LiveReloadServer: WebSocket hub that broadcasts reload signals to connected browsers at /dev/live-reload - ViteDevWatchService: now notifies LiveReloadServer after successful builds, with CSS-only vs full reload distinction - HtmlFileInertiaPageRenderer: injects a lightweight client script in dev mode that connects to the WebSocket and auto-reloads (CSS-only swap for Tailwind changes, full reload for JS/TSX) - CSP updated to allow WebSocket connections in development - Reconnection with exponential backoff (1s to 10s max)
Replace the build-and-refresh workflow with native Vite dev server HMR and .NET hot reload for a proper development experience: Vite dev server integration: - vite.dev.config.ts: Dev server config with React Fast Refresh, @tailwindcss/vite for CSS HMR, and module page resolution plugin - vite-plugin-module-hmr.ts: Resolves /_content/ module page imports to actual source files so Vite can serve them with HMR - ViteDevMiddleware: ASP.NET middleware that detects and proxies requests to Vite dev server (/@vite/, /@fs/, .tsx files) - HtmlFileInertiaPageRenderer: In Vite dev mode, strips import map, removes pre-built CSS link, and injects Vite HMR client scripts sm dev CLI command: - Runs dotnet watch (C# hot reload) + Vite dev server (frontend HMR) - Supports --no-vite, --no-dotnet, --vite-port flags - Graceful shutdown of all processes on Ctrl+C The LiveReload WebSocket fallback is preserved for running plain dotnet run without the Vite dev server.
DevCommand (sm dev):
- Graceful shutdown: SIGTERM first, wait 5s, then SIGKILL
- Second Ctrl+C force-kills immediately instead of being swallowed
- Unix: kill process group (-PID) so shell-spawned children get the signal
- Windows: taskkill /T for process tree termination
- Log PIDs on start and warn about survivors on failed shutdown
- Dispose all Process handles on exit
- Unregister event handlers to prevent accumulation
- ProcessExit handler force-kills to prevent orphans on unexpected exit
dev-orchestrator.mjs (npm run dev):
- Graceful SIGTERM then SIGKILL after 3s timeout
- Unix: process.kill(-pid, 'SIGTERM') for process group cleanup
- Windows: taskkill /T /F for process tree cleanup
- Handle SIGHUP (terminal close)
- process.on('exit') safety net force-kills all children
- uncaughtException handler kills children before crashing
- Track labels with processes for better diagnostics
DevCommand (sm dev) — C#: - Replace broken `kill -TERM -<pid>` (process group kill) with proper descendant tree walk: /proc/*/stat on Linux, pgrep -P on macOS - Send SIGTERM leaf-first so parents don't respawn before we reach them - Force-kill fallback: Kill(entireProcessTree: true) which uses /proc on Linux, libproc on macOS, NtQuerySystemInformation on Windows - If tree kill fails (permission denied on a child), fall back to killing just the direct process - Windows: taskkill /T (no /F) for graceful, Kill(tree) for force dev-orchestrator.mjs — Node: - Use detached: true on Unix so each child becomes a process group leader, making process.kill(-pid) actually work - Remove shell: true — it created intermediary sh processes that swallowed signals and made proc.pid point to the shell, not the actual dotnet/node process - Resolve npx binary directly instead of going through shell - Guard SIGHUP handler behind platform check (doesn't exist on Windows) - Fix 'exit' handler: use process.kill() (sync) not spawn() (async) since exit callbacks can't do async work - Handle ESRCH (already exited) gracefully instead of logging warnings
Before launching dotnet watch or Vite dev server, both sm dev and npm run dev now check if the required ports are free. If a port is occupied, the user sees what process holds it and can choose to kill it interactively. PortChecker (C#): - Linux: ss -tlnp (fast) with lsof fallback - macOS: lsof -iTCP:PORT -sTCP:LISTEN - Windows: netstat -ano + tasklist for process name - Parses launchSettings.json to discover ASP.NET ports dynamically - Kill uses Process.Kill(entireProcessTree: true) then verifies release dev-orchestrator.mjs (Node): - Same cross-platform port detection (lsof on Unix, netstat on Windows) - Linux: reads /proc/<pid>/comm for process name - macOS: ps -p <pid> -o comm= for process name - Windows: tasklist /FI for process name - Interactive Y/n prompt before killing - Parses launchSettings.json for ASP.NET port discovery
Bug fixes: - ViteEntryScripts: add CSP nonce to injected <script> tags — they were being blocked by the Content-Security-Policy header - ViteDevMiddleware: fix race condition on _lastCheck/_viteDetected using Interlocked.CompareExchange to prevent thundering-herd probes Code quality: - Extract DevToolsConstants.ViteDevServerKey shared between middleware and renderer — eliminates stringly-typed HttpContext.Items coupling - Replace magic-number _shutdownState (0/1/2) with ShutdownPhase constants for readability - Pass environment variables via parameter to StartProcess instead of checking label == "dotnet" string inside the method - Merge duplicate ShouldProxy/IsSourceFileRequest branches into single condition in ViteDevMiddleware - Simplify RenderPageAsync from 3-way if/else to direct expressions - Move mid-file ESM imports to top of dev-orchestrator.mjs Efficiency: - LiveReloadServer.NotifyReloadAsync: send to all WebSocket clients concurrently via Task.WhenAll instead of sequentially
Deploying simplemodule-website with
|
| Latest commit: |
6d859ce
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://652e9c45.simplemodule-website.pages.dev |
| Branch Preview URL: | https://claude-implement-hot-reload.simplemodule-website.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implement automatic browser refresh when Vite builds or Tailwind CSS
compilation completes during development. This eliminates the need
to manually refresh the browser after editing frontend code.
Key changes:
connected browsers at /dev/live-reload
builds, with CSS-only vs full reload distinction
dev mode that connects to the WebSocket and auto-reloads (CSS-only
swap for Tailwind changes, full reload for JS/TSX)